<?php
//http请求-验证IP
function verify_ip()
{
    static $illegal_ip = [], $illegal_count = 0, $lastWriteTime = 0;
    $ip = Helper::getIp();
    $allow_ip = GetC('http_ip');
    if (in_array($ip, $allow_ip)) return true;

    if (!isset($illegal_ip[$ip])) {
        $illegal_count++;
        $illegal_ip[$ip] = ['num' => 0, 'time' => 0];
    }
    $illegal_ip[$ip]['num']++; //记录非法ip请求次数
    $illegal_ip[$ip]['time'] = time();
    if ($illegal_count > 500) {
        $illegal_ip = array_sort($illegal_ip, 'time', 'desc', false); #时间倒序
        $illegal_ip = array_slice($illegal_ip, 0, 100, true); #截取前200
    }
    if ((time() - $lastWriteTime) > 10) {
        file_put_contents(ROOT . ROOT_DIR . '/illegal_ip.log', toJson($illegal_ip), LOCK_EX);
        $lastWriteTime = time();
    }
    return false;
}

function multiRead($req_buffer_array)
{
    return GetC('open_length_check') ? multiReadLen($req_buffer_array) : multiReadEof($req_buffer_array);
}

/**
 * 批量读取tcp连接数据 包长
 * @param $req_buffer_array
 * @return array
 */
function multiReadLen($req_buffer_array)
{
    set_error_handler(function(){}); #禁用错误处理
    $input = function ($buffer) {
        if (strlen($buffer) < 6) {
            return 0;
        }
        $unpack_data = unpack('Cnull/Ntotal_length/Cstart', $buffer);
        return $unpack_data['total_length'];
    };
    $encode = function ($buffer) {
        $total_length = 6 + strlen($buffer);
        return pack('CNC', 0x00, $total_length, 0x02) . $buffer;
    };
    $multi = [];
    foreach ($req_buffer_array as $address => $buffer) {
        $client = stream_socket_client("tcp://$address", $errno, $errmsg, 1);
        if (!$client) continue;

        stream_set_timeout($client, 0, 100000); #0.1
        fwrite($client, $encode(is_string($buffer) ? $buffer : json_encode($buffer))); #发送请求数据
        stream_set_blocking($client, 0);

        $multi[$address] = $client;
    }
    $read = $multi;
    $write = $except = $read_buffer = [];
    $time_start = microtime(true);
    $timeout = 6;#0.99;
    $pack_len = []; // 包长
    // 轮询处理数据
    while (count($read) > 0) {
        if (@stream_select($read, $write, $except, 0, 200000)) #0.2
        {
            foreach ($read as $address => $socket) {
                $buf = fread($socket, 8192);
                if (!$buf) {
                    if (feof($socket)) {
                        fclose($multi[$address]);
                        unset($multi[$address]);
                    }
                    continue;
                }

                if (!isset($read_buffer[$address])) {
                    $read_buffer[$address] = $buf;
                } else {
                    $read_buffer[$address] .= $buf;
                }
                if (!isset($pack_len[$address])) $pack_len[$address] = 0;
                if ($pack_len[$address] == 0) {
                    $pack_len[$address] = $input($read_buffer[$address]);
                }
                if ($pack_len[$address] > 0) {
                    $len = strlen($read_buffer[$address]);
                    if ($pack_len[$address] > $len) continue;

                    if ($pack_len[$address] <= $len) {
                        $read_buffer[$address] = substr($read_buffer[$address], 6, $pack_len[$address] - 6);
                        // 接收完毕清除
                        fclose($multi[$address]);
                        unset($multi[$address], $pack_len[$address]);
                    }
                }
            }
        }
        // 超时了
        if (microtime(true) - $time_start > $timeout) {
            break;
        }
        $read = $multi;
    }
    ksort($read_buffer);
    restore_error_handler(); #还原错误处理
    return $read_buffer;
}

/**
 * 批量读取tcp连接数据 EOF
 * @param $req_buffer_array
 * @param string $eof
 * @return array
 */
function multiReadEof($req_buffer_array, $eof="\n")
{
    set_error_handler(function(){}); #禁用错误处理
    $multi = [];
    foreach ($req_buffer_array as $address => $buffer) {
        $client = stream_socket_client("tcp://$address", $errno, $errmsg, 1);
        if (!$client) continue;

        stream_set_timeout($client, 0, 100000); #0.1
        fwrite($client, (is_string($buffer) ? $buffer : json_encode($buffer)) . $eof); #发送请求数据 使用eof
        stream_set_blocking($client, 0);

        $multi[$address] = $client;
    }
    $read = $multi;
    $write = $except = $read_buffer = [];
    $time_start = microtime(true);
    $timeout = 6;#0.99;
    // 轮询处理数据
    while (count($read) > 0) {
        if (@stream_select($read, $write, $except, 0, 200000)) #0.2
        {
            foreach ($read as $address => $socket) {
                $buf = fread($socket, 8192);
                if (!$buf) {
                    if (feof($socket)) {
                        fclose($multi[$address]);
                        unset($multi[$address]);
                    }
                    continue;
                }

                if (!isset($read_buffer[$address])) {
                    $read_buffer[$address] = $buf;
                } else {
                    $read_buffer[$address] .= $buf;
                }
                // 数据接收完毕
                if (($len = strlen($read_buffer[$address])) && $read_buffer[$address][$len - 1] === $eof) {
                    fclose($multi[$address]);
                    unset($multi[$address]);
                }
            }
        }
        // 超时了
        if (microtime(true) - $time_start > $timeout) {
            break;
        }
        $read = $multi;
    }
    ksort($read_buffer);
    restore_error_handler(); #还原错误处理
    return $read_buffer;
}

/**
 * 格式文本统计数据
 * @param $str
 * @param $date
 * @param null $count
 * @param int $minute 间隔x分钟统计 最小1分钟 最大60
 * @return array
 */
function formatStatistic($str, $date, &$count = null, $minute=5)
{
    if ($minute < 1 || $minute > 60) $minute = 5;
    $seconds = $minute * 60;
    // 总的合计
    $count = [
        'total_count' => 0,
        'total_cost_time' => 0,
        'total_avg_time' => 0,
        'suc_rate' => 0,
        'suc_count' => 0,
        'suc_cost_time' => 0,
        'suc_avg_time' => 0,
        'fail_count' => 0,
        'fail_cost_time' => 0,
        'fai_avg_time' => 0,
        'code' => [],
        'slow_times' => 0,
        'max_cost_time' => 0,
        'min_cost_time' => 1,
    ];
    // time:[suc_count:xx,suc_cost_time:xx,fail_count:xx,fail_cost_time:xx]
    $data = [];
    $st_explode = explode("\n", $str);
    // 汇总计算
    foreach ($st_explode as $line) {
        // line = IP time suc_count suc_cost_time fail_count fail_cost_time code_json
        $line_data = explode("\t", $line);
        if (!isset($line_data[6])) {
            continue;
        }
        $time_line = $line_data[1];
        $time_line = ceil($time_line / $seconds) * $seconds; //取每 $minute 分钟的时间戳
        $suc_count = $line_data[2];
        $suc_cost_time = $line_data[3];
        $fail_count = $line_data[4];
        $fail_cost_time = $line_data[5];
        $tmp_code_map = json_decode($line_data[6], true);
        $slow_times = isset($line_data[7]) ? $line_data[7] : 0;
        $max_cost_time = isset($line_data[8]) ? $line_data[8] : 0;
        $min_cost_time = isset($line_data[9]) ? $line_data[9] : 0;
        if (!isset($data[$time_line])) {
            $data[$time_line] = [
                'time' => date('Y-m-d H:i:s', $time_line),
                'total_count'=>0, 'suc_count' => 0, 'suc_cost_time' => 0, 'fail_count' => 0, 'fail_cost_time' => 0, 'slow_times' => 0, 'max_cost_time' => 0, 'min_cost_time' => 1
            ];
        }
        $data[$time_line]['slow_times'] += $slow_times;
        if ($data[$time_line]['max_cost_time'] < $max_cost_time) $data[$time_line]['max_cost_time'] = $max_cost_time;
        if ($data[$time_line]['min_cost_time'] > $min_cost_time) $data[$time_line]['min_cost_time'] = $min_cost_time;

        $data[$time_line]['total_count'] += $suc_count + $fail_count;
        $data[$time_line]['suc_count'] += $suc_count;
        $data[$time_line]['suc_cost_time'] += $suc_cost_time;
        $data[$time_line]['fail_count'] += $fail_count;
        $data[$time_line]['fail_cost_time'] += $fail_cost_time;

        if (is_array($tmp_code_map)) { // 统计code
            foreach ($tmp_code_map as $code => $num) {
                if (!isset($count['code'][$code])) {
                    $count['code'][$code] = 0;
                }
                $count['code'][$code] += $num;
            }
        }
    }
    // 按照时间排序
    // ksort($data);
    // time => [total_count:xx,suc_count:xx,suc_avg_time:xx,fail_count:xx,fail_avg_time:xx,percent:xx]

    // 计算成功率 耗时
    foreach ($data as $time_line => $item) {
        $total_count = $item['total_count'];
        //total_avg_time  suc_avg_time fail_avg_time precent
        $data[$time_line]['total_avg_time'] = $total_count == 0 ? 0 : number_format(($item['suc_cost_time'] + $item['fail_cost_time']) / $total_count, 6);
        $data[$time_line]['suc_avg_time'] = $item['suc_count'] == 0 ? $item['suc_count'] : number_format($item['suc_cost_time'] / $item['suc_count'], 6);
        $data[$time_line]['fail_avg_time'] = $item['fail_count'] == 0 ? 0 : number_format($item['fail_cost_time'] / $item['fail_count'], 6);
        $data[$time_line]['precent'] = $total_count == 0 ? 0 : number_format($item['suc_count'] * 100 / $total_count, 4); //成功率

        $count['slow_times'] += $item['slow_times'];
        if ($count['max_cost_time'] < $item['max_cost_time']) $count['max_cost_time'] = $item['max_cost_time'];
        if ($count['min_cost_time'] > $item['min_cost_time']) $count['min_cost_time'] = $item['min_cost_time'];

        $count['suc_count'] += $item['suc_count'];
        $count['suc_cost_time'] += $item['suc_cost_time'];
        $count['fail_count'] += $item['fail_count'];
        $count['fail_cost_time'] += $item['fail_cost_time'];
    }

    $count['total_count'] = $count['suc_count'] + $count['fail_count'];
    $count['total_cost_time'] = round($count['suc_cost_time'] + $count['fail_cost_time'],6);
    $count['total_avg_time'] = $count['total_count'] == 0 ? $count['total_count'] : number_format($count['total_cost_time'] / $count['total_count'], 6);
    $count['suc_avg_time'] = $count['suc_count'] == 0 ? $count['suc_count'] : number_format($count['suc_cost_time'] / $count['suc_count'], 6);
    $count['fai_avg_time'] = $count['fail_count'] == 0 ? 0 : number_format($count['fail_cost_time'] / $count['fail_count'], 6);
    $count['suc_rate'] = $count['total_count'] == 0 ? 100 : number_format(($count['suc_count'] / $count['total_count'])*100, 4);
    $none = [
        'time' => '',
        'total_count'=>0, 'suc_count' => 0, 'suc_cost_time' => 0, 'fail_count' => 0, 'fail_cost_time' => 0, 'slow_times' => 0, 'max_cost_time' => 0, 'min_cost_time' => 0,
        'total_avg_time' => 0,
        'suc_avg_time' => 0,
        'fail_avg_time' => 0,
        'precent' => 0,
    ];
    $time_point = strtotime($date);
    $num = 1440/$minute;
    // 1天每5分钟的数据
    for ($i = 0; $i < $num; $i++) {
        if (!isset($data[$time_point])) {
            $none['time'] = date('Y-m-d H:i:s', $time_point);
            $data[$time_point] = $none;
        }
        $time_point += $seconds;
    }
    ksort($data);
    return $data;
}

/**
 * 获取IP源
 * @return array
 */
function getIpList(){
    $ip = Q('ip'); //有指定ip
    if($ip){
        if(!strpos($ip, ':')){
            $ip .= ':'.GetC('data_udp_port');
        }
        $_REQUEST['address'] = $_GET['address'] = $ip;
        return ['tcp://'.$ip=>1];
    }
    return (array)json_decode(@file_get_contents(ROOT . ROOT_DIR . '/ip_list'), true);
}

/**
 * 获取tcp数据读取源
 * @param null $ipList
 * @return array
 */
function getTcpIpList($ipList=null){
    $ip = [];
    if($ipList===null) $ipList = getIpList();
    foreach ($ipList as $key => $v) {
        if (strpos($key, 'tcp://') === 0) {
            $ip[] = substr($key, 6);
        }
    }
    return $ip;
}

/**
 * 获取path
 * @param $type
 * @param $module
 * @param $date
 * @param string $name
 * @param string|array $address
 * @param null $req_arr
 * @return array
 */
function getPath($type, $module, $date, $name='', $address=null, &$req_arr=null){
    $post = [
        'cmd' => 'get_path',
        'type' => $type,
        'module' => $module,
        'date' => $date,
        'name' => $name,
    ];
    if ($address) { //单服务地址查询
        $req_arr = is_array($address) ? array_fill_keys($address, $post) : [$address => $post];
    } else {
        $req_arr = array_fill_keys(getTcpIpList(), $post);
    }
    $list = [];
    $ret_arr = multiRead($req_arr);
    foreach ($ret_arr as $address => $v) {
        $ret = json_decode(rtrim($v), true);
        if(!$ret) continue;
        $isSqlite = isset($ret['--all']) && $ret['--all']>0;
        foreach ($ret as $path => $num) {
            if($isSqlite && strpos($path,'/')){
                $parent = substr($path, 0, strrpos($path, '/')+1).'*';
                if(isset($list[$parent])){
                    $list[$parent] += $num;
                } else{
                    $list[$parent] = $num;
                }
            }
            if(isset($list[$path])){
                $list[$path] += $num;
            } else{
                $list[$path] = $num;
            }
        }
    }

    ksort($list, SORT_NATURAL | SORT_FLAG_CASE);

    return $list;
}

/**
 * 获取 x 天
 * @param int $day
 * @return array
 */
function getDateList($day=7){
    $dateList = [];
    for ($i = $day-1; $i >= 1; $i--) {
        $dateList[] = date('Y-m-d', strtotime("-$i day"));
    }
    $dateList[] = date('Y-m-d');
    return $dateList;
}